[アップデート]AWS Lambdaでストリーミングな応答が可能になりました
初めに
昨日のアップデートでAWS Lambdaは実行結果の返却値を一括の応答ではなくストリーミングな徐々に応答するようなことが可能となりました。
いざ日本語に直そうとすると微妙に難しいタイトルで実際の公式の翻訳がどうなるか次第では少しタイトルを調整するかもしれません。
これまでの方式ではLambdaの機能としては処理完了まで返却値を返すことができず、そういった機能が必要な場合はWebSocket等別の手段をユーザ側で実装する必要がありました。
今回のアップデートではTransfer-Encoding: chunked
形式による返却に対応しHTTP/1.1の仕様の範囲内で徐々に値を返却できるようになりました。
またこの方式は応答サイズの上限が従来の6MBではなく20MBまでの対応となるためより大きなレスポンスを返すことができるようです。
Configuring a Lambda function to stream responses/
Currently, Lambda supports response streaming only on Node.js 14.x, Node.js 16.x, and Node.js 18.x managed runtimes. You can also use a custom runtime with a custom Runtime API integration to stream responses.
なお現時点でLambda側ではNode.jsの14.x以降のバージョンのみが対応してしており他のマネージドランタイムでは対応していないようです。
SAM CLIも対応済み
AWS公式ブログの方がSAM使っていたので気になってリポジトリの方を確認したところ、SAM CLIの1.79.0
がリリースされており既に本機能に対応していました。
呼び出し方法は限られている
https://aws.amazon.com/jp/blogs/compute/introducing-aws-lambda-response-streaming/
Neither API Gateway nor Lambda’s target integration with Application Load Balancer support chunked transfer encoding. It therefore does not support faster TTFB for streamed responses. You can, however, use response streaming with API Gateway to return larger payload responses, up to API Gateway’s 10 MB limit. To implement this, you must configure an HTTP_PROXY integration between your API Gateway and a Lambda function URL, instead of using the LAMBDA_PROXY integration.
なおALBおよびLambdaプロキシ統合のAPI Gatewayは今回追加されたレスポンス形式に対応していないため現時点では利用できません。
Lambda関数URLの直接アクセス、HTTP統合のAPI Gateway経由の呼び出しといった別の手段でアクセスする必要があります。
https://aws.amazon.com/jp/blogs/compute/introducing-aws-lambda-response-streaming/
You can use the AWS SDK to stream responses directly from the new Lambda InvokeWithResponseStream API. This provides additional functionality such as handling midstream errors. This can be helpful when building, for example, internal microservices. Response streaming is supported with the AWS SDK for Java 2.x, AWS SDK for JavaScript v3, and AWS SDKs for Go version 1 and version 2.
一部のSDKでは今回の応答方式に対応した呼び出しAPIであるInvokeWithResponseStream
が実装されておりそちらを経由して実行することも可能です。
料金
ストリーミングレスポンスは応答サイズに応じて追加料金がかかります。
本記事執筆時点で東京リージョンの料金は$0.008/GB
となるようです。
最新の情報は料金ページをご確認ください。
(執筆時点では英語ページのみに記載有)
設定
本機能を利用するにあたり呼び出しモードの設定が必要となります。
呼び出しモードの変更は関数URLの作成から行うことができます。
設定は作成後にも変更可能なため既存の関数URLを変更することが可能です。
コードサンプル
せっかくSAMも対応しているのでSAM経由でデプロイします。
SAMテンプレート
InvokeMode
の値で先ほどの画面相当の設定が可能です。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Globals: Function: Timeout: 15 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello-world/ Handler: app.lambdaHandler Runtime: nodejs18.x Architectures: - arm64 Metadata: BuildMethod: esbuild BuildProperties: Minify: true Target: "es2020" Sourcemap: true EntryPoints: - app.ts StreamingUrl: Type: AWS::Lambda::Url Properties: TargetFunctionArn: !Ref HelloWorldFunction AuthType: AWS_IAM InvokeMode: RESPONSE_STREAM
Lambdaコード
シンプルに徐々に返却されることがわかるように500ミリ秒ごとにhello worldを返却するコードにしました。
(型はドキュメントから推定しているため誤っている可能性があります)
import {Context} from 'aws-lambda'; import fs from 'fs'; export const lambdaHandler = awslambda.streamifyResponse ( async (event: JSON, responseStream: fs.WriteStream, context: Context): Promise<void> => { for (let idx = 1; idx < 11; idx++) { await new Promise(resolve => setTimeout(resolve, 500)); responseStream.write(`${idx} times hello world\n`); } responseStream.write("----END Stream----"); responseStream.end(); } );
従来と異なり呼び出し元への値の引き渡しがreturn経由ではない点に注意してください。
https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html
The responseStream object is a Node.js writableStream. As with any such stream, you should use the pipeline() method.
...
While responseStream offers the write() method to write to the stream, we recommend that you use pipeline() wherever possible. Using pipeline() ensures that the writable stream is not overwhelmed by a faster readable stream.
今回は一旦WriteStream.write()
を直接呼び出していますが、本来はStream.pipeline()
を経由しての書き込みが推奨されています。
デプロイ
SAM経由でデプロイでも特別なコマンドは必要なく通常通りsam deploy
で可能です。
アクセス確認
IAM認証をしようとすると少し手間になるので確認の際に一時的に認証を外してcurlコマンドでアクセスを確認しました。
一気に値が返却されるのではなく徐々に値が返却されていることがわかります。
実のところ公式ブログの表現としてはvia chunked transfer encoding
となっておりTransfer-Encoding: chunked
になることは明記されていませんでしたが--verbose
で応答を確認してみると思っていた通り利用が確認できました。
$ curl --verbose https://pmrjgmyjlejct5nzedxb477o3e0gkyar.lambda-url.ap-northeast-1.on.aws/ .... * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Sat, 08 Apr 2023 13:14:11 GMT < Content-Type: application/octet-stream < Transfer-Encoding: chunked < Connection: keep-alive ... < 1 times hello world 2 times hello world 3 times hello world 4 times hello world 5 times hello world 6 times hello world 7 times hello world 8 times hello world 9 times hello world 10 times hello world * Connection #0 to host pmrjgmyjlejct5nzedxb477o3e0gkyar.lambda-url.ap-northeast-1.on.aws left intact
終わりに
まだ利用できる範囲は狭いですがAWS Lambdaでユーザ側で別の手段を用意することなくストリーミングな応答を利用することができるようになりました。
サーバサイドのみではなくクライアント側としても別の技術を用意する必要がなく通常のHTTP通信の範囲でリアルタイムな処理ができたりと嬉しいアップデートです。
一方でTransfer-Encoding: chunked
による応答については実はHTTP/1.1までの対応となりHTTP/2では使用が禁止されているという一面があります。
今後のアップデートでどうなって行くのかという部分は見守る必要があるかもしれません。